package controller

import (
	"SelectCourse/dao"
	"SelectCourse/types"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis"
	"github.com/jinzhu/gorm"
	"log"
	"net/http"
	"strconv"
	"time"
)

type MyCourse struct {
	gorm.Model
	//CourseID   string `gorm:"column:courseid;type:varchar(64);" json:"courseid"`
	Coursename string `gorm:"column:coursename;type:varchar(64);" json:"coursename"`
	TeacherID  string `gorm:"column:teacherID;type:varchar(64);" json:"teacherid"`
	//Password   string `gorm:"column:password;type:varchar(20);" json:"password"`
	Capacity int `gorm:"column:capacity;type:int(64);" json:"capacity"`
}

func (MyCourse) TableName() string {
	return "tcourse"
}

//测试用
type IntCourse struct {
	id         int64
	coursename string
	teacherid  string
}

/*

 */
func Get(c *gin.Context) {
	//创建一个表，看是啥样的
	//dao.Db.AutoMigrate(&MyCourse{})
	//Coursetemp := MyCourse{
	//	Model:      gorm.Model{},
	//	CourseID:   "12",
	//	Coursename: "music",
	//	TeacherID:  "158",
	//	Capacity:   0,
	//}
	//dao.Db.Create(&Coursetemp)
	var request types.GetCourseRequest
	var course MyCourse
	var response types.GetCourseResponse
	var num uint64
	if err := c.ShouldBind(&request); err != nil {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	//将request的string转为int64
	num, err := strconv.ParseUint(request.CourseID, 10, 64)
	if err != nil {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	//result1 := dao.Db.Table("tcourse").Select("id", "coursename").Where("id = ?", num).Scan(&course)
	//查询语句 go翻译为：SELECT * FROM `tcourse`  WHERE `tcourse`.`deleted_at` IS NULL AND ((courseid = 1))
	//sqlyog能查出来，放不进course
	result := dao.Db.Where("id = ?", num).Find(&course)
	//result1 := dao.Db.First(&course, "id = ?", str) //条件查询
	if result.Error != nil {
		response.Code = types.CourseNotExisted
		c.JSON(http.StatusOK, response)
		return
	}
	//fmt.Printf("%v\n", course.ID)         //0
	//fmt.Printf("%v\n", course.teacherID)  //""
	//fmt.Printf("%v\n", course.coursename) //""
	response.Data.ID = uint(num)
	response.Data.Name = course.Coursename
	response.Data.TeacherID = course.TeacherID
	response.Data.Cap = course.Capacity
	response.Code = types.OK
	c.JSON(http.StatusOK, response)
	return
}

func BindCourse(c *gin.Context) {
	var request types.BindCourseRequest
	var response types.BindCourseResponse
	var course MyCourse
	if err := c.ShouldBind(&request); err != nil {
		response.Code = types.ParamInvalid
		fmt.Println(err)
		c.JSON(http.StatusOK, response)
		return
	}
	var num int64
	num, err := strconv.ParseInt(request.CourseID, 10, 64)
	if err != nil {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	result := dao.Db.Where("id = ?", num).Find(&course)
	if result.Error != nil {
		response.Code = types.CourseNotExisted
		c.JSON(http.StatusOK, response)
		return
	}
	if course.TeacherID != "" {
		response.Code = types.CourseHasBound
		c.JSON(http.StatusOK, response)
		return
	}
	course.TeacherID = request.TeacherID
	dao.Db.Save(&course)
	response.Code = types.OK
	c.JSON(http.StatusOK, response)
	return
}

func UnbindCourse(c *gin.Context) {
	var request types.UnbindCourseRequest
	var response types.UnbindCourseResponse
	var course MyCourse
	if err := c.ShouldBind(&request); err != nil {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}

	var num int64
	num, err := strconv.ParseInt(request.CourseID, 10, 64)
	if err != nil {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	result := dao.Db.Where("id = ?", num).Find(&course)
	if result.Error != nil {
		response.Code = types.CourseNotExisted
		c.JSON(http.StatusOK, response)
		return
	}
	if course.TeacherID == "" {
		response.Code = types.CourseNotBind
		c.JSON(http.StatusOK, response)
		return
	}

	if course.TeacherID != request.TeacherID {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	course.TeacherID = ""
	dao.Db.Save(&course)
	response.Code = types.OK
	c.JSON(http.StatusOK, response)
	return
}

/**
 * @Description
 * @Author zkm
 * @Date 2022/2/9 14:01
 **/
func CreateCourse(c *gin.Context) {
	var getCourse types.CreateCourseRequest
	var response types.CreateCourseResponse
	if err := c.Bind(&getCourse); err != nil {
		log.Println(err)
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	if len(getCourse.Name) > 20 || getCourse.Cap <= 0 || getCourse.Cap >= 100000 {
		response.Code = types.ParamInvalid
		c.JSON(http.StatusOK, response)
		return
	}
	var course types.TCourse
	log.Println("before adding")
	course.Name = getCourse.Name
	course.Cap = getCourse.Cap
	dao.Db.Where("coursename = ?", course.Name).First(&course)
	log.Println(course)
	if course.Model.ID != 0 {
		log.Println("课程重复！")
		response.Code = types.UserHasExisted
		c.JSON(http.StatusOK, response)
		return
	}

	result := dao.Db.Create(&course)
	if result.Error != nil {
		response.Code = types.UnknownError
		c.JSON(http.StatusOK, response)
		return
	}
	response.Data.CourseID = string(strconv.Itoa(int(course.Model.ID)))

	response.Code = types.OK
	c.JSON(http.StatusOK, response)
	return
}

func GetTeacherCourse(c *gin.Context) {
	var getTeacherCourseRequest types.GetTeacherCourseRequest
	var getTeacherCourseResponse types.GetTeacherCourseResponse
	if err := c.Bind(&getTeacherCourseRequest); err != nil {
		log.Println(err)
		getTeacherCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusBadRequest, getTeacherCourseResponse)
		return
	}
	teacherId := getTeacherCourseRequest.TeacherID
	var dataList []*types.TCourse
	dataList = dao.GetTeacherCourse(teacherId)
	getTeacherCourseResponse.Code = types.OK
	getTeacherCourseResponse.Data.CourseList = dataList
	c.JSON(http.StatusOK, getTeacherCourseResponse)
	return
}

func GetStudentCourse(c *gin.Context) {
	var getStudentCourseRequest types.GetStudentCourseRequest
	var getStudentCourseResponse types.GetStudentCourseResponse
	if err := c.Bind(&getStudentCourseRequest); err != nil {
		log.Println(err)
		getStudentCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusBadRequest, getStudentCourseResponse)
		return
	}
	studentId := getStudentCourseRequest.StudentID
	courseList := dao.GetStudentCourse(studentId)
	getStudentCourseResponse.Code = types.OK
	getStudentCourseResponse.Data.CourseList = courseList
	c.JSON(http.StatusOK, getStudentCourseResponse)
	return
}

//原生方法，不加redis的版本
func BookCourse(c *gin.Context) {
	var bookCourseRequest types.BookCourseRequest
	var bookCourseResponse types.BookCourseResponse
	if err := c.ShouldBind(&bookCourseRequest); err != nil {
		log.Println(err)
		bookCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusBadRequest, bookCourseResponse)
		return
	}

	studentId := bookCourseRequest.StudentID
	courseId := bookCourseRequest.CourseID

	var result bool
	result = false

	dao.Db.Transaction(func(tx *gorm.DB) error {
		courses, err := dao.GetCoursesCapacityWithTx(courseId, tx)
		if err != nil {
			return err
		}
		if len(courses) == 0 {
			result = false
		} else {
			course := courses[0]
			cap := course.Cap
			oldCourses, err := dao.GetStudentCourseWithTx(studentId, tx)
			if err != nil {
				return err
			}
			if len(oldCourses) > 0 && strconv.Itoa(int(oldCourses[0].Model.ID)) == courseId {
				result = true
			} else {
				if cap <= 0 {
					result = false
				} else {
					result = true
					err = dao.UpdateCourseCapWithTx(courseId, cap-1, tx)
					if err != nil {
						return err
					}

					if len(oldCourses) == 0 {
						err = dao.InsertStudentCourseWithTx(studentId, courseId, tx)
						if err != nil {
							return err
						}
					} else {
						var oldCourseCap int
						oldCourseCap = oldCourses[0].Cap
						err = dao.UpdateCourseCapWithTx(strconv.Itoa(int(oldCourses[0].Model.ID)), oldCourseCap+1, tx)
						if err != nil {
							return err
						}
						err = dao.UpdateStudentCourseWithTx(studentId, courseId, tx)
						if err != nil {
							return err
						}
					}
				}
			}
		}
		return nil
	})

	bookCourseResponse.Code = types.OK
	if !result {
		bookCourseResponse.Code = types.CourseNotAvailable
	}
	c.JSON(http.StatusOK, bookCourseResponse)
	return
}

//利用redis抢课,仅用redis不同步数据的版本
func RedisBookCourse(c *gin.Context) {
	//请求和响应参数
	var bookCourseRequest types.BookCourseRequest
	var bookCourseResponse types.BookCourseResponse
	//绑定请求
	if err := c.ShouldBind(&bookCourseRequest); err != nil {
		bookCourseResponse.Code = types.ParamInvalid
		log.Println(err)
		c.JSON(http.StatusBadRequest, bookCourseResponse)
		return
	}
	studentId := bookCourseRequest.StudentID
	courseId := bookCourseRequest.CourseID
	//判断是否为空
	if len(studentId) == 0 || len(courseId) == 0 {
		bookCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 用于Redis存储的课程库存key
	courseKey := "courseId_" + courseId
	//用exist判断是否存在Key
	n, err := dao.Rdb.Exists(courseKey).Result()
	//key存在返回1,不存在返回0，若不存在，则把（课程ID-课程余量）
	if n == 0 {
		var course types.TCourse
		courseidInt, _ := strconv.Atoi(courseId)
		course.ID = uint(courseidInt)
		dao.Db.First(&course)
		//课程名为空则代表不存在,这里还有点问题，因为利用&course查询，id已经设置了，无法用Id判断不存在，可以改为sql的方式
		if len(course.Name) == 0 {
			bookCourseResponse.Code = types.CourseNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		//将（课程ID-课程余量）写入Redis，最后一个0表示永不过期，还需要考虑
		dao.Rdb.Set(courseKey, course.Cap, 0)
	}
	//从Redis获取课程余量
	courseCap, _ := dao.Rdb.Get(courseKey).Result()
	courseCapInt, _ := strconv.Atoi(courseCap)
	//如果课程余量小于0，返回
	if courseCapInt <= 0 {
		bookCourseResponse.Code = types.CourseNotAvailable
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 选课学生key
	studentKey := "studentId_" + studentId
	//用exist判断是否存在Key
	n1, err := dao.Rdb.Exists(studentKey).Result()
	//key存在返回1,不存在返回0，不存在将学生Key放进redis里，这里还想把学生已经抢到的课放进redis里，正在思考性能问题,学生的key-value，key：学生Id,value:set集合，学生抢过的课程
	if n1 == 0 {
		var student types.TMember
		studentIdInt, _ := strconv.Atoi(studentId)
		student.ID = uint(studentIdInt)
		dao.Db.First(&student)
		if len(student.Username) == 0 || student.UserType != types.Student {
			bookCourseResponse.Code = types.StudentNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		var result []types.TCourse
		err1 := dao.Db.Table("tcourse").
			Joins("left join `studentcourse` on studentcourse.courseid = tcourse.id").
			Select("tcourse.id, tcourse.coursename, tcourse.capacity, tcourse.TeacherID").
			Where("userid = ?", studentId).Find(&result).Error
		if err1 == nil {
			for _, curCourse := range result {
				dao.Rdb.SAdd(studentKey, curCourse.ID)
				dao.Rdb.Expire(studentKey, 5*time.Minute)
			}
		} else {
			log.Println(err1)
		}
	}
	//如果Reids中学生已经抢过该课
	if dao.Rdb.SIsMember(studentKey, courseId).Val() {
		bookCourseResponse.Code = types.StudentHasCourse
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	//利用watch和事务防止超卖
	err2 := dao.Rdb.Watch(func(tx *redis.Tx) error {
		_, err := tx.Get(courseKey).Result()
		if err != nil && err != redis.Nil {
			log.Println(err)
			return err
		}
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			pipe.Decr(courseKey)
			pipe.SAdd(studentKey, bookCourseRequest.CourseID)
			dao.Rdb.Expire(studentKey, 5*time.Minute)
			return nil
		})
		return err
	}, courseKey)

	if err2 != nil {
		bookCourseResponse.Code = types.UnknownError
		c.JSON(http.StatusOK, bookCourseResponse)
		log.Println("tx exec failed:", err)
		return
	}
	bookCourseResponse.Code = types.OK
	c.JSON(http.StatusOK, bookCourseResponse)
	return
	//待讨论问题，什么时候redis和数据库同步？定时同步，还是学生抢到课了就同步。
}

//利用redis抢课,延时双删版本，同时使用redis中的watch、数据库中的事务，思路有点混乱。暂时放弃
func RedisBookCourseDelayDoubleDelete(c *gin.Context) {
	//请求和响应参数
	var bookCourseRequest types.BookCourseRequest
	var bookCourseResponse types.BookCourseResponse
	//绑定请求
	if err := c.ShouldBind(&bookCourseRequest); err != nil {
		bookCourseResponse.Code = types.ParamInvalid
		log.Println(err)
		c.JSON(http.StatusBadRequest, bookCourseResponse)
		return
	}
	studentId := bookCourseRequest.StudentID
	courseId := bookCourseRequest.CourseID
	//判断是否为空
	if len(studentId) == 0 || len(courseId) == 0 {
		bookCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 用于Redis存储的课程库存key
	courseKey := "courseId_" + courseId
	//用exist判断是否存在Key
	n, _ := dao.Rdb.Exists(courseKey).Result()
	//key存在返回1,不存在返回0，若不存在，则把（课程ID-课程余量）
	if n == 0 {
		var course types.TCourse
		courseidInt, _ := strconv.Atoi(courseId)
		course.ID = uint(courseidInt)
		dao.Db.First(&course)
		//课程名为空则代表不存在,这里还有点问题，因为利用&course查询，id已经设置了，无法用Id判断不存在，可以改为sql的方式
		if len(course.Name) == 0 {
			bookCourseResponse.Code = types.CourseNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		//将（课程ID-课程余量）写入Redis,最后一个参数是过期时间
		dao.Rdb.Set(courseKey, course.Cap, 5*time.Minute)
	}
	//从Redis获取课程余量
	courseCap, _ := dao.Rdb.Get(courseKey).Result()
	courseCapInt, _ := strconv.Atoi(courseCap)
	//如果课程余量小于0，返回
	if courseCapInt <= 0 {
		bookCourseResponse.Code = types.CourseNotAvailable
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 选课学生key
	studentKey := "studentId_" + studentId
	//用exist判断是否存在Key
	n1, _ := dao.Rdb.Exists(studentKey).Result()
	//key存在返回1,不存在返回0，不存在将学生Key放进redis里，这里还想把学生已经抢到的课放进redis里，正在思考性能问题,学生的key-value，key：学生Id,value:set集合，学生抢过的课程
	if n1 == 0 {
		var student types.TMember
		studentIdInt, _ := strconv.Atoi(studentId)
		student.ID = uint(studentIdInt)
		dao.Db.First(&student)
		if len(student.Username) == 0 || student.UserType != types.Student {
			bookCourseResponse.Code = types.StudentNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		var result []types.TCourse
		err1 := dao.Db.Table("tcourse").
			Joins("left join `studentcourse` on studentcourse.courseid = tcourse.id").
			Select("tcourse.id, tcourse.coursename, tcourse.capacity, tcourse.TeacherID").
			Where("userid = ?", studentId).Find(&result).Error
		if err1 == nil {
			for _, curCourse := range result {
				dao.Rdb.SAdd(studentKey, curCourse.ID)
				dao.Rdb.Expire(studentKey, 5*time.Minute)
			}
		} else {
			log.Println(err1)
		}
	}
	//如果Reids中学生已经抢过该课
	if dao.Rdb.SIsMember(studentKey, courseId).Val() {
		bookCourseResponse.Code = types.StudentHasCourse
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	var result bool
	result = false
	//利用watch和事务防止超卖，这里思路有点混乱，思考1:watch保证在watch的值不变时里面的操作才能生效，这里watch的是课程容量，如果使用延时双删，改都是在数据库里面改，是否还有必要
	err2 := dao.Rdb.Watch(func(tx *redis.Tx) error {
		_, err := tx.Get(courseKey).Result()
		if err != nil && err != redis.Nil {
			log.Println(err)
			return err
		}
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			//删除Redis中的课程数据和选课数据
			pipe.Del(courseKey)
			pipe.Del(studentKey)
			//事务更新数据库数据
			dao.Db.Transaction(func(tx *gorm.DB) error {
				courses, err := dao.GetCoursesCapacityWithTx(courseId, tx)
				if err != nil {
					return err
				}
				if len(courses) == 0 {
					result = false
				} else {
					course := courses[0]
					cap := course.Cap
					oldCourses, err := dao.GetStudentCourseWithTx(studentId, tx)
					if err != nil {
						return err
					}
					if len(oldCourses) > 0 && strconv.Itoa(int(oldCourses[0].Model.ID)) == courseId {
						result = true
					} else {
						if cap <= 0 {
							result = false
						} else {
							result = true
							err = dao.UpdateCourseCapWithTx(courseId, cap-1, tx)
							if err != nil {
								return err
							}
							if len(oldCourses) == 0 {
								err = dao.InsertStudentCourseWithTx(studentId, courseId, tx)
								if err != nil {
									return err
								}
							} else {
								var oldCourseCap int
								oldCourseCap = oldCourses[0].Cap
								err = dao.UpdateCourseCapWithTx(strconv.Itoa(int(oldCourses[0].Model.ID)), oldCourseCap+1, tx)
								if err != nil {
									return err
								}
								err = dao.UpdateStudentCourseWithTx(studentId, courseId, tx)
								if err != nil {
									return err
								}
							}
						}
					}
				}
				return nil
			})
			//pipe.Decr(courseKey)
			//pipe.SAdd(studentKey,bookCourseRequest.CourseID)
			return nil
		})
		return err
	}, courseKey)

	if err2 != nil {
		bookCourseResponse.Code = types.UnknownError
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	bookCourseResponse.Code = types.OK
	c.JSON(http.StatusOK, bookCourseResponse)
	return
	//待讨论问题，什么时候redis和数据库同步？定时同步，还是学生抢到课了就同步。
}

//利用redis抢课,redis watch+mysql事务+延时双删版本
func RedisBookCourseDelayDoubleDeleteFinal(c *gin.Context) {
	//请求和响应参数
	var bookCourseRequest types.BookCourseRequest
	var bookCourseResponse types.BookCourseResponse
	//绑定请求
	if err := c.ShouldBind(&bookCourseRequest); err != nil {
		bookCourseResponse.Code = types.ParamInvalid
		log.Println(err)
		c.JSON(http.StatusBadRequest, bookCourseResponse)
		return
	}
	studentId := bookCourseRequest.StudentID
	courseId := bookCourseRequest.CourseID
	//判断是否为空
	if len(studentId) == 0 || len(courseId) == 0 {
		bookCourseResponse.Code = types.ParamInvalid
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 用于Redis存储的课程库存key
	courseKey := "courseId_" + courseId
	//用exist判断是否存在Key
	n, _ := dao.Rdb.Exists(courseKey).Result()
	//key存在返回1,不存在返回0，若不存在，则把（课程ID-课程余量）
	var course types.TCourse
	if n == 0 {
		courseidInt, _ := strconv.Atoi(courseId)
		course.ID = uint(courseidInt)
		result := dao.Db.First(&course)
		//查不到结果会返回 ErrRecordNotFound 错误
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			bookCourseResponse.Code = types.CourseNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		//将（课程ID-课程余量）写入Redis，最后一个0表示永不过期，还需要考虑
		dao.Rdb.Set(courseKey, course.Cap, 5*time.Minute)
	}
	//从Redis获取课程余量
	courseCap, _ := dao.Rdb.Get(courseKey).Result()
	courseCapInt, _ := strconv.Atoi(courseCap)
	//如果课程余量小于0，返回
	if courseCapInt <= 0 {
		bookCourseResponse.Code = types.CourseNotAvailable
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	// 选课学生key
	studentKey := "studentId_" + studentId
	//用exist判断是否存在Key
	n1, _ := dao.Rdb.Exists(studentKey).Result()
	//key存在返回1,不存在返回0，不存在将学生Key放进redis里，这里还想把学生已经抢到的课放进redis里，正在思考性能问题,学生的key-value，key：学生Id,value:set集合，学生抢过的课程
	if n1 == 0 {
		var student types.TMember
		studentIdInt, _ := strconv.Atoi(studentId)
		student.ID = uint(studentIdInt)
		dbResult := dao.Db.First(&student)
		if errors.Is(dbResult.Error, gorm.ErrRecordNotFound) || student.UserType != types.Student || student.IsDelete == 1 {
			bookCourseResponse.Code = types.StudentNotExisted
			c.JSON(http.StatusOK, bookCourseResponse)
			return
		}
		var result []types.TCourse
		err1 := dao.Db.Table("tcourse").
			Joins("left join `studentcourse` on studentcourse.courseid = tcourse.id").
			Select("tcourse.id, tcourse.coursename, tcourse.capacity, tcourse.TeacherID").
			Where("userid = ?", studentId).Find(&result).Error
		if err1 == nil {
			for _, curCourse := range result {
				dao.Rdb.SAdd(studentKey, curCourse.ID)
				dao.Rdb.Expire(studentKey, 5*time.Minute)
			}
		} else {
			log.Println(err1)
		}
	}
	//如果Reids中学生已经抢过该课
	if dao.Rdb.SIsMember(studentKey, courseId).Val() {
		bookCourseResponse.Code = types.StudentHasCourse
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	//以上逻辑没有捕获到，说明该学生可以抢这个课
	//延时双删代码块
	//删除Redis中的课程key和学生key,在删除redis时使用watch，如果redis中这两个键的值有变动则不再进行
	//存一个cap备份，在往数据库中写的时候进行校验
	capBackup, _ := dao.Rdb.Get(courseKey).Int()
	err2 := dao.Rdb.Watch(func(tx *redis.Tx) error {
		_, err := tx.Get(courseKey).Result()
		if err != nil && err != redis.Nil {
			log.Println(err)
			return err
		}
		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
			dao.Rdb.Del(courseKey)
			dao.Rdb.Del(studentKey)
			return nil
		})
		return err
	}, courseKey)
	if err2 != nil {
		bookCourseResponse.Code = types.UnknownError
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}

	//更新数据库
	err := dao.Db.Transaction(func(tx *gorm.DB) error {
		var course types.TCourse
		err3 := tx.Table("tcourse").Select("capacity").Where("id = ?", courseId).First(&course).Error
		if err3 != nil {
			return err3
		}
		//如果这个时候的数据库容量和redis中的课程容量不一致了就回滚
		if course.Cap != capBackup && course.Cap > 0 {
			tx.Rollback()
			return errors.New("和redis数据不一致了")
		}
		//插入选课数据
		err := dao.InsertStudentCourseWithTx(studentId, courseId, tx)
		if err != nil {
			return err
		}
		//容量-1
		err = dao.UpdateCourseCapWithTx(courseId, course.Cap-1, tx)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		bookCourseResponse.Code = types.UnknownError
		c.JSON(http.StatusOK, bookCourseResponse)
		return
	}
	bookCourseResponse.Code = types.OK
	c.JSON(http.StatusOK, bookCourseResponse)
	//开个新线程，延时再删除一次
	go delayDelete(courseKey, studentKey)
	return
}
func delayDelete(courseKey string, studentKey string) {
	//延时再删一次
	myT := time.NewTimer(7 * time.Second)
	// 重置定时器为 100ms,100ms后再删除一次 100000000: 100ms
	myT.Reset(100000000)
	<-myT.C
	//再次执行
	dao.Rdb.Del(courseKey)
	dao.Rdb.Del(studentKey)
}
